/**
 * 格式化字段表单元素封装
 * @author rxliuli
 */
Vue.component('rx-select', {
    model: {
        prop: 'value',
        event: 'change',
    },
    props: {
        value: [Number, String],
        defaultValue: String,
        map: {
            type: Map,
            defaultValue: new Map(),
        },
    },
    computed: {
        innerValue: {
            get() {
                if (this.value === undefined || this.value === null || (this.map && this.map.size === 0)) {
                    return this.defaultValue ? '' : Array.from(this.map.keys())[0]
                }
                return this.value
            },
            set(val) {
                this.$emit('change', val)
            },
        },
    },
    watch: {
        map(val) {
            if (val && val.size === 0) {
                this.$emit('change', this.defaultValue ? '' : Array.from(this.map.keys())[0])
            }
        },
    },
    template: `<select
  v-model="innerValue"
>
  <option
    v-if="defaultValue"
    value=""
  >{{defaultValue}}
  </option>
  <option
    v-for="[k,v] in map"
    :value="k"
    :key="k"
  >{{v}}
  </option>
</select>
    `,
})

Vue.component('rx-radio', {
    model: {
        prop: 'value',
        event: 'change',
    },
    props: {
        value: [Number, String],
        name: {
            type: String,
            default: () => `rx-radio-${rx.autoIncrement()}`,
        },
        map: Map,
    },
    computed: {
        innerValue: {
            get() {
                if (rx.isNullOrUndefined(this.value)) {
                    let val = Array.from(this.map.keys())[0]
                    this.$emit('change', val)
                    return val
                }
                return this.value
            },
            set(val) {
                this.$emit('change', val)
            },
        },
    },
    template: `<span>
  <label v-for="[k,v] in map">
    <input
      v-model="innerValue"
      :name="name"
      :value="k"
      type="radio"
    />
    {{v}}
  </label>
</span>
    `,
})

/**
 * 单个元素校验器类
 */
class RxElemValidator {
    /**
     * 构造函数
     * @param {String|Symbol} name 校验 form 对象的字段
     * @param {Function} checkFn 检查函数
     * @param {String} msg 错误消息
     */
    constructor({name, checkFn, msg}) {
        this.name = name
        this.checkFn = checkFn
        this.msg = msg
    }

    /**
     * 创建一个单个元素校验器类
     * @param {String|Symbol} name 校验 form 对象的字段
     * @param {Function} checkFn 检查函数
     * @param {String} msg 错误消息
     * @return {RxElemValidator} RxElemValidator 实例
     */
    static create(name, msg, checkFn) {
        return new RxElemValidator({
            name: name,
            checkFn: checkFn,
            msg: msg,
        })
    }

    /**
     * 校验指定字段是非空的
     * @param name
     * @param msg
     * @return {RxElemValidator}
     */
    static notBlank(name, msg) {
        return RxElemValidator.create(name, msg, rx.compose(rx.toString, rx.stringValidator.isBlank))
    }

    /**
     * 限制长度
     * @param name
     * @param msg
     * @param max 最大长度
     * @param [min] 最小长度，默认为 0
     * @return {RxElemValidator}
     */
    static limitLen(name, msg, max, min = 0) {
        return RxElemValidator.create(name, msg, v => {
            const len = rx.toString(v).length
            return len > max || len < min
        })
    }

    static mayBeBlank(name, msg, checkFn) {
        const {
            CombinedPredicate: {
                not,
                and,
            },
            stringValidator: {
                isBlank,
            },
        } = rx
        return RxElemValidator.create(name, msg, and(not(isBlank), checkFn))
    }
}

/**
 * 表单校验器
 */
class RxFormValidator {
    /**
     * 构造函数
     * @param {Object} form 校验对象
     * @param {Array.<RxElemValidator>} rxElemValidators 校验器列表
     * @param {Function} callback 错误后的回调函数
     */
    constructor({form, rxElemValidators, callback = msg => rxPrompt.warningMsg(msg)}) {
        this.form = form
        this.rxElemValidators = rxElemValidators
        this.callback = callback
    }

    /**
     * 校验
     * @return {Boolean} 是否通过校验
     */
    async validate() {
        for (let {name, checkFn, msg} of this.rxElemValidators) {
            const result = await checkFn(this.form[name], this.form)
            if (result) {
                await this.callback(msg)
                return false
            }
        }
        return true
    }

    /**
     * 生成一个校验器
     * @param {Array.<RxElemValidator>} rxElemValidators 校验器列表
     * @param {Function} callback 错误后的回调函数
     * @return {Function} 校验函数
     */
    static validator({rxElemValidators, callback}) {
        const rxFormValidator = new RxFormValidator({
            rxElemValidators: rxElemValidators,
            callback,
        })
        return form => {
            rxFormValidator.form = form
            return rxFormValidator.validate()
        }
    }

    static generate(rxElemValidators) {
        const rxFormValidator = new RxFormValidator({
            rxElemValidators: rxElemValidators,
        })
        return form => {
            rxFormValidator.form = form
            return rxFormValidator.validate()
        }
    }
}